/**
 * @class 天文学坐标系实体
 * @author DuskyHuang 圣子
*/

import { _decorator, Component, Node, Vec3, renderer, MeshRenderer, Color, Vec4, IVec3Like, Camera, Mat3, Material, GeometryRenderer } from 'cc';
import { DEG_PER_HOUR, DEG_PERIGON, DEG_PI, DEG_RIGHT, LAYER_BG_3D, DEG_RIGHT_2 } from 'const';
import { CelestialLabel } from '../ui/items/celestial-label';
import { UIProxy, gizmos } from 'proxies';
import { setLabelStyle, setLayer } from 'helpers';
const { ccclass, property } = _decorator;

@ccclass('cocosmos.Coordinates')
export class Coordinates extends Component {

    @property(MeshRenderer) public grids: MeshRenderer = null;

    protected onLoad(): void {
        this._pass = this.grids?.material?.passes[0];
        this._handleGC = this._pass.getHandle('gridColor');
        this._handleGD = this._pass.getHandle('gridDivision');
        this._handleEC = this._pass.getHandle('equatorColor');
        const SCALE = DEEP_DIST / 10;
        this.node.setWorldScale(SCALE, SCALE, SCALE);
        setLayer(this.node, LAYER_BG_3D);
    }

    public setKeyPrefix(prefix: string,): this {
        this._prefix = prefix;
        return this;
    }

    // public setNames(equator: i18nKey, postfix: string): this {
    //     this._equatorKey = 'coords@' + equator;
    //     this._polarPostfix = postfix;
    //     return this;
    // }

    public setView(center: Node, camera: Camera, renderer: GeometryRenderer): this {
        this._center = center;
        this._camera = camera;
        this._renderer = renderer;
        return this;
    }

    public setRotation(euler: IVec3Like): this {
        this.node.setWorldRotationFromEuler(euler.x, euler.y, euler.z);
        Mat3.fromMat4(this._transform, this.node.worldMatrix);
        return this;
    }

    public setColors(grids: Color, equator: Color): this {
        this._pass.setUniform(this._handleGC, this._grid_c
            .set(grids.r, grids.g, grids.b, grids.a).multiplyScalar(1 / 255));
        this._pass.setUniform(this._handleEC, this._equator_c
            .set(equator.r, equator.g, equator.b, equator.a).multiplyScalar(1 / 255));
        return this;
    }

    public setDivision(div: Vec4): this {
        this._pass.setUniform(this._handleGD, this._grid_d.set(div));
        return this;
    }

    public set gridAlpha(arg: num.pct) {
        if (this._grid_c.w === arg) return;
        this._grid_c.w = arg;
        this._pass.setUniform(this._handleGC, this._grid_c);
    }

    public set equatorAlpha(arg: num.pct) {
        if (this._equator_c.w === arg) return;
        this._equator_c.w = arg;
        this._pass.setUniform(this._handleEC, this._equator_c);
    }

    protected onDisable(): void {
        this.freeDegreesLabels();
        this.freeEquatorLabels();
        this.freePolarLabels();
    }

    protected lateUpdate(): void {
        const { showCoordGrids, showCoordEquator, showCoordPolar, showCoordAxis, lineStrength } = gizmos;
        if (showCoordGrids) {
            this.createDegreesLabels(this._grid_d.x === 24);
            this.gridAlpha = lineStrength;
        } else {
            this.freeDegreesLabels();
            this.gridAlpha = 0;
        }
        if (showCoordEquator) {
            this.createEquatorLabels();
            this.equatorAlpha = lineStrength;
        } else {
            this.freeEquatorLabels();
            this.equatorAlpha = 0;
        }
        if (showCoordPolar) this.createPolarLabels(); else this.freePolarLabels();
        if (showCoordAxis) {
            this._renderer.addLine(Vec3.multiplyScalar(v3a, this.node.up, DEEP_DIST),
                Vec3.negate(v3b, v3a), Color.WHITE, false)
        }
    }

    public createDegreesLabels(hourAngle: boolean): this {
        if (this._gridsLabels.length > 0) return;
        this._v4_color();
        // horizontal
        const hcc = Math.round(DEG_PERIGON / this._grid_d.x);
        for (let deg = 0; deg < DEG_PERIGON; deg += hcc) {
            const str = hourAngle ? `+${deg / DEG_PER_HOUR}h` : `+${deg}°`;
            const label = setLabelStyle(CelestialLabel.claim('coord', str), 'coord');
            label.target = this._center;
            label.camera = this._camera;
            // label.string = str;
            label.pure = str;
            label.color = color;
            this._gridsLabels.push(label);
            const rad = Math.parseRadian(deg);
            v3a.set(Math.sin(rad), 0, Math.cos(rad));
            Vec3.transformMat3(v3a, v3a, this._transform);
            label.setOffset(v3a);
        }
        // vertical
        const vcc = Math.round(DEG_PI / this._grid_d.y);
        for (let deg = 0; deg <= DEG_RIGHT; deg += vcc) {
            const num = deg === DEG_RIGHT ? 2 : 8;
            for (let i = 0; i < num; i++) {
                const nth = i % 2 === 0;
                let str = `${nth ? '+' : '-'}${deg}°`;
                if (str === '-0°' || str === '+0°') continue;
                const label = setLabelStyle(CelestialLabel.claim('coord', str), 'coord');
                label.target = this._center;
                label.camera = this._camera;
                // label.string = str;
                label.pure = str;
                label.color = color;
                const rad = Math.parseRadian(deg);
                const hor = Math.cos(rad), ver = Math.sin(rad);
                const x = i < 2 ? hor : i < 4 ? -hor : 0;
                const y = nth ? ver : -ver;
                const z = i > 5 ? hor : i > 3 ? -hor : 0;
                v3a.set(x, y, z);
                this._gridsLabels.push(label);
                Vec3.transformMat3(v3a, v3a, this._transform);
                label.setOffset(v3a);
            }
        }
        return this;
    }

    public createEquatorLabels(): this {
        if (this._equatorLabels.length > 0) return;
        this._v4_color();
        for (let deg = 0; deg < DEG_PERIGON; deg += DEG_RIGHT_2) {
            const label = CelestialLabel.claim('coord', this._prefix + '-equator');
            label.target = this._center;
            label.camera = this._camera;
            // label.string = this._equatorName;
            label.key = `coords@${this._prefix}-eq`;
            setLabelStyle(label, 'ruler', 'equator');
            label.color = color;
            const rad = Math.parseRadian(deg);
            v3a.set(Math.sin(rad), 0, Math.cos(rad));
            Vec3.transformMat3(v3a, v3a, this._transform);
            label.setOffset(v3a);
            this._equatorLabels.push(label);
        }
        return this;
    }

    public createPolarLabels(): this {
        if (this._polarLabels.length > 0) return;
        this._v4_color();
        ['s', 'n'].forEach(dir => {
            // let key: i18nKey = pre + this._polarPostfix;
            // if (key === '北天顶') key = '天顶';
            // if (key === '南天顶') key = '天底';
            const label = CelestialLabel.claim('coord', this._prefix + '-' + dir + '-pole');
            label.target = this._center;
            label.camera = this._camera;
            // label.string = key;
            label.key = `coords@${this._prefix}-${dir}`;
            setLabelStyle(label, 'ruler', 'polar');
            label.color = color;
            v3a.set(0, dir === 'n' ? 1 : -1, 0);
            Vec3.transformMat3(v3a, v3a, this._transform);
            label.setOffset(v3a);
            this._polarLabels.push(label);
        });
        return this;
    }

    public freeEquatorLabels(): this { return this._free(this._equatorLabels); }

    public freeDegreesLabels(): this { return this._free(this._gridsLabels); }

    public freePolarLabels(): this { return this._free(this._polarLabels); }

    private _v4_color(): void {
        const { x, y, z } = v4a.set(this._grid_c).multiplyScalar(255);
        color.set(x, y, z, 255);
    }

    private _free(labels: CelestialLabel[]): this {
        labels.forEach(label => label?.free());
        labels.length = 0;
        return this;
    }

    // private _polarPostfix: string = '';
    // private _equatorKey: string = '';

    private _prefix: string = '';

    private _pass: renderer.Pass;
    private _handleGC: handle;
    private _handleGD: handle;
    private _handleEC: handle;
    private _grid_c: Vec4 = new Vec4(1, 1, 1, 1);
    private _grid_d: Vec4 = new Vec4(36, 18, 2, 2);
    private _equator_c: Vec4 = new Vec4(1, 1, 1, 1);
    private _gridsLabels: CelestialLabel[] = [];
    private _polarLabels: CelestialLabel[] = [];
    private _equatorLabels: CelestialLabel[] = [];
    private _transform: Mat3 = new Mat3;
    private _center: Node;
    private _camera: Camera;
    private _renderer: GeometryRenderer;
}

const { DEEP_DIST } = UIProxy;
const color = new Color;
const v4a = new Vec4;
const v3a = new Vec3;
const v3b = new Vec3;